﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace DotNEToolkit
{
    public class KeyboardDevice : SingletonObject<KeyboardDevice>
    {
        public delegate void KeyDownEventDelegate(Keys key);

        #region Win32 Interop

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        #endregion

        #region 实例变量

        private bool installed;
        private LowLevelKeyboardProc _proc;
        private List<KeyDownEventDelegate> keyDownDelegates;

        #endregion

        #region 构造方法

        public KeyboardDevice() 
        {
            this.keyDownDelegates = new List<KeyDownEventDelegate>();
        }

        #endregion

        #region 公开接口

        public void AddHook(KeyDownEventDelegate handler)
        {
            if (!this.installed) 
            {
                _proc = new LowLevelKeyboardProc(this.HookCallback);

                using (Process curProcess = Process.GetCurrentProcess())
                {
                    using (ProcessModule curModule = curProcess.MainModule)
                    {
                        SetWindowsHookEx(WH_KEYBOARD_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
                    }
                }

                this.installed = true;
            }

            this.keyDownDelegates.Add(handler);
        }

        #endregion

        #region 事件处理器

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                Keys vkCode = (Keys)Marshal.ReadInt32(lParam);

                foreach (KeyDownEventDelegate @delegate in this.keyDownDelegates)
                {
                    @delegate(vkCode);
                }
            }
            return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }

        #endregion
    }
}

